package com.eastmoney.emis.edda.infrastructure.edda.service;

import com.eastmoney.emis.edda.application.vo.EddaBindVo;
import com.eastmoney.emis.edda.application.vo.EddaOtpRegenerationVo;
import com.eastmoney.emis.edda.config.HsbcEddaConfig;
import com.eastmoney.emis.edda.domain.entity.EddaApply;
import com.eastmoney.emis.edda.domain.entity.EddaBind;
import com.eastmoney.emis.edda.domain.event.EddaAuthEvent;
import com.eastmoney.emis.edda.domain.event.EddaOtpConfirmEvent;
import com.eastmoney.emis.edda.domain.event.EddaOtpRegenerationEvent;
import com.eastmoney.emis.edda.domain.event.EventPublisher;
import com.eastmoney.emis.edda.domain.repository.HsbcEddaRepository;
import com.eastmoney.emis.edda.domain.service.IdService;
import com.eastmoney.emis.edda.domain.service.edda.EddaAuthService;
import com.eastmoney.emis.edda.domain.service.edda.EddaBindCommand;
import com.eastmoney.emis.edda.domain.service.edda.EddaOtpConfirmCommand;
import com.eastmoney.emis.edda.domain.service.edda.EddaOtpRegenerationCommand;
import com.eastmoney.emis.edda.domain.valueobject.EddaApplyStatus;
import com.eastmoney.emis.edda.domain.valueobject.EddaBindStatus;
import com.eastmoney.emis.edda.domain.valueobject.TimeFormatter;
import com.eastmoney.emis.edda.infrastructure.exception.EddaException;
import com.eastmoney.emis.edda.infrastructure.exception.EddaExceptionStatus;
import com.eastmoney.emis.edda.infrastructure.hsbc.HsbcEddaApi;
import com.eastmoney.emis.edda.infrastructure.hsbc.dto.*;
import com.eastmoney.emis.edda.infrastructure.message.MessageSender;
import com.eastmoney.emis.logger.LoggerFactory;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.Objects;
import java.util.function.Predicate;

/**
 * @description: Description
 * @projectName: EDDA-HSBC
 * @package: com.eastmoney.emis.edda.infrastructure.edda.service
 * @author: 汪程程
 * @date: 2022/10/9 10:14 AM
 * @version: 1.0
 */
@Service
public class EddaAuthServiceImpl implements EddaAuthService {
    private static final Logger log = LoggerFactory.getLogger(EddaAuthServiceImpl.class);

    @Autowired
    private HsbcEddaConfig config;
    @Autowired
    private HsbcEddaRepository hsbcEddaRepository;
    @Autowired
    private HsbcEddaApi hsbcEddaApi;
    @Autowired
    private IdService idService;
    @Autowired
    private HsbcApiErrorResolver errorResolver;
    @Autowired
    private EventPublisher eventPublisher;
    @Autowired
    private MessageSender sender;


    @Override
    public EddaBindVo auth(EddaBindCommand command) {
        AuthReq authReq = buildAuthReq(command);
        AuthResp authResp = hsbcEddaApi.authorization(authReq);
        EddaApply eddaApply = buildEddaApply(authReq, authResp, command);
        hsbcEddaRepository.save(eddaApply);
        publishAuthEvent(authResp, eddaApply);
        errorResolver.resolveAndTrow(authResp);

        return EddaBindVo.EddaBindVoBuilder.create()
                .withRegisterId(eddaApply.getRegisterId())
                .withOtpId(authResp.OtpIdentificationNumber)
                .build();
    }

    @Override
    public void auth(EddaApply eddaApply) {
        eddaApply.status(EddaApplyStatus.PROCESSING);
        eddaApply.setRegisterId(idService.generateId());
        AuthReq authReq = new AuthReq(eddaApply, config.getCreditorAccount());
        AuthResp authResp = hsbcEddaApi.authorization(authReq);
        hsbcEddaRepository.save(eddaApply);
        publishAuthEvent(authResp, eddaApply);
    }

    @Override
    public EddaBindVo authOffline(EddaBindCommand command) {
        if (command.isHsbc()) {
            // reject
            throw new EddaException(EddaExceptionStatus.LIMIT_APPLY_WITHIN_SERVING_TIME);
        }
        EddaApply eddaApply = buildEddaApplyOffline(command);
        hsbcEddaRepository.save(eddaApply);
        publishAuthEvent(null, eddaApply);
        return EddaBindVo.EddaBindVoBuilder.create().build();
    }

    private EddaApply buildEddaApplyOffline(EddaBindCommand command) {
        EddaApply eddaApply = new EddaApply();
        eddaApply.status(EddaApplyStatus.INIT);
        eddaApply.setClientId(command.getClientId());
        eddaApply.setBankCode(command.getBankCode());
        eddaApply.setBankAccount(command.getBankAccount());
        eddaApply.setCustomEngName(command.getCustomEngName());
        eddaApply.setCreditReference(command.getCreditorReference());
        eddaApply.setMobile(command.getMobile());
        eddaApply.setDebtorId(command.getDebtorPrivateId());
        eddaApply.setDebtorSchemeName(command.getDebtorPrivateIdSchemeName());
        eddaApply.setLanguage(command.getLanguage());
        eddaApply.setCurrency("HKD");
        eddaApply.setMaximumAmount("99999999");
        eddaApply.setFrequencyType("ADHO");
        eddaApply.setGrantExpiryDate(LocalDate.parse("9999-12-31", TimeFormatter.YYYY_MM_DD));
        return eddaApply;
    }

    private void publishAuthEvent(AuthResp authResp, EddaApply eddaApply) {
        EddaAuthEvent event = new EddaAuthEvent(eddaApply.getClientId());
        event.setRefEid(eddaApply.getEid());
        event.setStatus(eddaApply.getStatus());
        if (authResp != null) {
            event.setResult(authResp.result());
            event.setNote(authResp.contextString());
        }
        eventPublisher.publish(event);
    }

    private AuthReq buildAuthReq(EddaBindCommand eddaBindCommand) {
        AuthReq authReq = new AuthReq(eddaBindCommand, config.getCreditorAccount());
        authReq.MerchantRequestIdentification = idService.generateId();
        return authReq;
    }

    @Override
    public void otpConfirm(EddaOtpConfirmCommand command) {
        EddaApply eddaApply = hsbcEddaRepository.findEddaApplyByClientIdAndRegisterId(command.getClientId(), command.getRegisterId());
        // 状态判断
        Predicate<EddaApply> predicate = Objects::isNull;
        predicate = predicate.or(r -> EddaApplyStatus.OTP_UNCONFIRMED != EddaApplyStatus.parse(r.getStatus()));
        checkEddaApplyStatus(eddaApply, predicate);

        OTPConfirmResp otpConfirmResp = hsbcEddaApi.otpConfirmation(buildOtpConfirmReq(command, eddaApply));
        publishOtpConfirmEvent(command, eddaApply, otpConfirmResp);
        errorResolver.resolveAndTrow(otpConfirmResp);
        // TODO change status according by otpConfirmResp
        eddaApply.status(EddaApplyStatus.PROCESSING);
        eddaApply.setOtpNumber(command.getOtpNumber());
        hsbcEddaRepository.save(eddaApply);

        EddaBind eddaBind = hsbcEddaRepository.findEddaBindByClientIdAndBankAccount(command.getClientId(), command.getBankCode(), command.getBankAccount());
        if (!eddaBind.finished()) {
            eddaBind.setStatus(EddaBindStatus.PROCESSING.value());
            hsbcEddaRepository.save(eddaBind);
        }

    }

    private void publishOtpConfirmEvent(EddaOtpConfirmCommand command, EddaApply eddaApply, OTPConfirmResp otpConfirmResp) {
        EddaOtpConfirmEvent confirmEvent = new EddaOtpConfirmEvent(command);
        confirmEvent.setRefEid(eddaApply.getEid());
        confirmEvent.setStatus(eddaApply.getStatus());
        confirmEvent.setResult(otpConfirmResp.result());
        confirmEvent.setNote(otpConfirmResp.contextString());
        eventPublisher.publish(confirmEvent);
    }

    private void checkEddaApplyStatus(EddaApply eddaApply, Predicate<EddaApply> predicate) {
        if (predicate.test(eddaApply)) {
            throw new EddaException(EddaExceptionStatus.INVALID_EDDA_BIND);
        }
    }

    private OTPConfirmReq buildOtpConfirmReq(EddaOtpConfirmCommand command, EddaApply eddaApply) {
        OTPConfirmReq otpConfirmReq = new OTPConfirmReq();
        otpConfirmReq.MandateIdentification = eddaApply.getMandateId();
        otpConfirmReq.CreditorAccount = new AccountDto(config.getCreditorAccount());
        otpConfirmReq.OtpIdentificationNumber = command.getOtpId();
        otpConfirmReq.OtpPassword = command.getOtpPassword();
        return otpConfirmReq;
    }

    @Override
    public EddaOtpRegenerationVo otpRegeneration(EddaOtpRegenerationCommand command) {
        EddaApply eddaApply = hsbcEddaRepository.findEddaApplyByClientIdAndRegisterId(command.getClientId(), command.getRegisterId());
        Predicate<EddaApply> predicate = Objects::isNull;
        predicate = predicate.or(r -> EddaApplyStatus.OTP_UNCONFIRMED != EddaApplyStatus.parse(r.getStatus()));
        checkEddaApplyStatus(eddaApply, predicate);

        OTPRegenerationReq otpRegenerationReq = new OTPRegenerationReq(eddaApply.getMandateId(),
                eddaApply.getLanguage(),
                new AccountDto(config.getCreditorAccount()));
        OTPRegenerationResp resp = hsbcEddaApi.reGenerateOtpCode(otpRegenerationReq);
        publishEddaOtpRegenerationEvent(eddaApply, resp);
        errorResolver.resolveAndTrow(resp);
        return new EddaOtpRegenerationVo(eddaApply.getRegisterId(), resp.OtpIdentificationNumber);
    }

    private void publishEddaOtpRegenerationEvent(EddaApply eddaApply, OTPRegenerationResp otpRegenerationResp) {
        EddaOtpRegenerationEvent event = new EddaOtpRegenerationEvent(eddaApply.getClientId());
        event.setRefEid(eddaApply.getEid());
        event.setStatus(eddaApply.getStatus());
        event.setResult(otpRegenerationResp.result());
        event.setNote(otpRegenerationResp.contextString());
        eventPublisher.publish(event);
    }


    private EddaApply buildEddaApply(AuthReq req, AuthResp authResp, EddaBindCommand command) {
        EddaApply eddaApply = new EddaApply();
        eddaApply.status(command.isHsbc() ? EddaApplyStatus.OTP_UNCONFIRMED : EddaApplyStatus.PROCESSING);
        eddaApply.setClientId(command.getClientId());
        eddaApply.setBankCode(command.getBankCode());
        eddaApply.setBankAccount(command.getBankAccount());
        eddaApply.setCustomEngName(command.getCustomEngName());
        eddaApply.setRegisterId(req.MerchantRequestIdentification);
        eddaApply.setCreditReference(req.CreditorReference);
        eddaApply.setMobile(req.DebtorMobileNumber);
        eddaApply.setDebtorId(req.DebtorPrivateIdentification);
        eddaApply.setDebtorSchemeName(req.DebtorPrivateIdentificationSchemeName);
        eddaApply.setCurrency(req.MaximumAmountCurrency);
        eddaApply.setMaximumAmount(req.MaximumAmount);
        eddaApply.setFrequencyType(req.Occurrences.FrequencyType);
        eddaApply.setLanguage(req.SmsLanguageCode);
        eddaApply.setMandateId(authResp.MandateIdentification);
        eddaApply.setGrantExpiryDate(LocalDate.parse(req.Occurrences.DurationToDate, TimeFormatter.YYYY_MM_DD));
        return eddaApply;
    }
}
